package com.recommendation.domain;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.NamingException;

import org.apache.commons.lang.time.DateUtils;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;

import com.recommendation.db.DBUtils;
import com.recommendation.db.Dao;
import com.recommendation.service.RecommendEngine;
import com.recommendation.service.RecommendItemsProvider;
import com.recommendation.service.TaobaoService;
import com.recommendation.taobao.TaobaoProxy;
import com.taobao.api.ApiException;
import com.taobao.api.TaobaoResponse;
import com.taobao.api.domain.Item;
import com.taobao.api.response.ItemRecommendAddResponse;
import com.taobao.api.response.ItemsOnsaleGetResponse;
import com.taobao.api.response.ShopRemainshowcaseGetResponse;

public class RecommendJob implements Job{

	static Logger _logger = Logger.getLogger(RecommendJob.class.getName());
	
	private static final Hashtable<Long, Integer> invalidTable = new Hashtable<Long, Integer>();
	
	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDataMap dataMap = context.getJobDetail().getJobDataMap();
		long userId = dataMap.getLongValue("userId");
		_logger.info("userId: " + userId);
		try {
			String topSession = Dao.INSTANCE.getTopSession(userId);
			ShopRemainshowcaseGetResponse remainRsp = TaobaoProxy.getRemainShowCaseCount(topSession);
			if (remainRsp.isSuccess())
			{
				invalidTable.remove(userId);
				
				long remainCount = remainRsp.getShop().getRemainCount();
				_logger.info("remain: " + remainCount);
				short version = Dao.INSTANCE.getVersion(userId);
				SpecialItems special = Dao.INSTANCE.getSpecialItems(userId);
				_logger.info("topSession: " + topSession);
				List<Item> excludedItems = null;
				List<Item> mustItems = null;
				if (special != null)
				{
					if (special.isNotItemsEnabled() && version > 1)
					{
						excludedItems = RecommendItemsProvider.INSTANCE.getExcludeItems(special, topSession);
						remainCount = unrecommendNotItems(userId, topSession, excludedItems, remainCount);
					}
					if (special.isMustItemsEnabled() && version > 1)
					{
						mustItems = RecommendItemsProvider.INSTANCE.getMustItems(special, topSession);
						remainCount = recommendMustItems(userId, topSession, mustItems, remainCount);
					}
				}
				RecommendScope scope = Dao.INSTANCE.getRecommendScope(userId);
				_logger.info("scope: " + scope);
				if (scope == null)
				{
					scope = new RecommendScope();
				}
				
				RecommendMode mode = Dao.INSTANCE.getRecommendMode(userId);
				_logger.info("mode: " + mode);
				if (mode == null)
				{
					mode = new RecommendMode();
				}
				if (mode.getMode() == RecommendMode.MODE_FILL)
				{	
					_logger.info("fill mode...");
					recommendInFillMode(userId, topSession, scope, excludedItems, version, remainCount);
				}
				else
				{
					_logger.info("optimize mode...");
					int unrecommededItemsCount = unrecommendNewItems(userId, topSession, scope, excludedItems, version, mustItems);
					remainCount = remainCount + unrecommededItemsCount;
					//remainCount = recommendInForceMode(userId, topSession, mode.getRecentPeriod(), scope, special, mustItems, version, remainCount);
					recommendInFillMode(userId, topSession, scope, excludedItems, version, remainCount);
				}
			}
			else
			{
				_logger.log(Level.SEVERE, TaobaoProxy.getError(remainRsp));
				if (isInvalidSession(remainRsp))
				{
					increaseInvalidCount(userId);
					checkSession(userId);
					return;
				}
			}
		} catch (ApiException e) {
			_logger.log(Level.SEVERE, "", e);
		} catch (NamingException e) {
			_logger.log(Level.SEVERE, "", e);
		} catch (SQLException e) {
			_logger.log(Level.SEVERE, "", e);
		} catch (Exception e) {
			_logger.log(Level.SEVERE, "", e);
		}
		clearLog(userId);
	}
	
	/**
	 * 
	 * @param userId
	 * @param topSession
	 * @param mustItems
	 * @return unrecommended items count
	 * @throws Exception 
	 */
	private int unrecommendNewItems(long userId, String topSession, RecommendScope scope, List<Item> excludeItems, short version, List<Item> mustItems) throws Exception
	{
		int unrecommendedItemsCount = 0;
		Item oldestItemNotInShowCase = null;
		List<Item> items = RecommendItemsProvider.INSTANCE.getItems(userId, scope, excludeItems, 1, topSession, version);
		if (items != null && !items.isEmpty())
		{
			oldestItemNotInShowCase = items.get(0);
		}
		if (oldestItemNotInShowCase == null)
		{
			return unrecommendedItemsCount;
		}
		ItemsOnsaleGetResponse hasShowcaseRsp = TaobaoProxy.getHasShowcaseItems(topSession, 200);
		if (hasShowcaseRsp.isSuccess())
		{
			List<Item> hasShowcaseButLongToBeDelistItems = hasShowcaseRsp.getItems();
			if (hasShowcaseButLongToBeDelistItems != null && !hasShowcaseButLongToBeDelistItems.isEmpty())
			{
				Item first = hasShowcaseButLongToBeDelistItems.get(0);
				_logger.info("first.delistTime: " + first.getDelistTime());
				_logger.info("oldestItemNotInShowCase.delistTime: " + oldestItemNotInShowCase.getDelistTime());
				if (first.getDelistTime() == null || oldestItemNotInShowCase.getDelistTime() == null || oldestItemNotInShowCase.getDelistTime().before(first.getDelistTime()))
				{
					for (Item item : hasShowcaseButLongToBeDelistItems)
					{
						if (!TaobaoService.contains(mustItems, item))
						{
							Date delistTime = item.getDelistTime();
							_logger.info("delistTime: " + delistTime);
							if (delistTime != null)
							{
								if (oldestItemNotInShowCase.getDelistTime().before(delistTime))
								{
									_logger.info("deleting recommend item: " + item.getNumIid() + ":" + item.getDelistTime());
									TaobaoProxy.deleteRecommendItem(item.getNumIid(), topSession);
									++unrecommendedItemsCount;
								}
								else
								{
									break;
								}
							}
						}
					}
				}
			}
		}
		else
		{
			_logger.log(Level.SEVERE, TaobaoProxy.getError(hasShowcaseRsp));
		}
		return unrecommendedItemsCount;
	}
	
	private long unrecommendNotItems(long userId, String topSession, List<Item> notItems, long remainCount) throws ApiException
	{
		if (notItems != null)
		{
			for (Item item : notItems)
			{
				if (item.getHasShowcase())
				{
					TaobaoProxy.deleteRecommendItem(item.getNumIid(), topSession);
					++remainCount;
				}
			}
		}
		return remainCount;
	}
	
	/**
	 * 
	 * @param userId
	 * @param topSession
	 * @param mustItems
	 * @param remainRsp
	 * @return remain showcases
	 * @throws ApiException
	 * @throws NamingException
	 * @throws SQLException
	 */
	private long recommendMustItems(long userId, String topSession, List<Item> mustItems, long remainCount) throws ApiException, NamingException, SQLException
	{
		if (mustItems != null && !mustItems.isEmpty())
		{
			_logger.info("all must count: " + mustItems.size());
			List<Item> mustItemsNotInShowcase = new ArrayList<Item>();
			for (Item item : mustItems)
			{
				if (!item.getHasShowcase())
				{
					mustItemsNotInShowcase.add(item);
				}
			}
			int mustItemsCount = mustItemsNotInShowcase.size();
			_logger.info("must items to be recommended count: " + mustItemsCount);
			if (mustItemsCount > 0)
			{
				if (mustItemsCount > remainCount)
				{
					ItemsOnsaleGetResponse hasShowcaseRsp = TaobaoProxy.getHasShowcaseItems(topSession, 200);
					if (hasShowcaseRsp.isSuccess())
					{
						List<Item> hasShowcaseButLongToBeDelistItems = hasShowcaseRsp.getItems();
						if (hasShowcaseButLongToBeDelistItems != null)
						{
							for (Item item : hasShowcaseButLongToBeDelistItems)
							{
								if (!TaobaoService.contains(mustItems, item))
								{
									_logger.info(userId + " - deleting recommend item: " + item.getNumIid() + ":" + item.getDelistTime());
									TaobaoProxy.deleteRecommendItem(item.getNumIid(), topSession);
									if (++remainCount >= mustItemsCount)
									{
										break;
									}
								}
							}
						}
					}
					else
					{
						_logger.log(Level.SEVERE, TaobaoProxy.getError(hasShowcaseRsp));
					}
				}
				for (Item item : mustItemsNotInShowcase)
				{
					TaobaoResponse rsp = recommend(item, userId, topSession);
					if (rsp.isSuccess())
					{
						--remainCount;
					}
					else if (isOverflow(rsp))
					{
						break;
					}
				}
			}
		}
		return remainCount;
	}
	
	private void recommendInFillMode(long userId, String topSession, RecommendScope scope, List<Item> excludeItems, short version, long remainCount) throws Exception
	{
		if (remainCount > 0)
		{
			_logger.info("remain showcase count: " + remainCount);
			List<Item> items = RecommendItemsProvider.INSTANCE.getItems(userId, scope, excludeItems, (int)remainCount, topSession, version);
			if (items != null)
			{
				_logger.info("get items count: " + items.size());
				for (Item item : items)
				{
					_logger.info("recommending item: " + item.getNumIid() + "; user: " + userId);
					TaobaoResponse rsp = recommend(item, userId, topSession);
					if (isOverflow(rsp))
					{
						break;
					}
				}
			}
			else
			{
				_logger.warning("items is null");
			}
		}
	}
	
	private boolean isOverflow(TaobaoResponse rsp)
	{
		if (rsp != null && !rsp.isSuccess())
		{
			String subCode = rsp.getSubCode();
			return subCode != null && subCode.contains("ERROR_MORE_THAN_ALLOWED_RECOMMEND_NUM");		
		}
		return false;
	}
	
	/**
	 * 
	 * @param userId
	 * @param topSession
	 * @param recentPeriod
	 * @param scope
	 * @param special
	 * @param mustItems
	 * @param version
	 * @param remainCount
	 * @return remain showcases
	 * @throws Exception
	 */
	private long recommendInForceMode(long userId, String topSession, int recentPeriod, RecommendScope scope, List<Item> excludeItems, List<Item> mustItems, short version, long remainCount) throws Exception
	{
		List<Item> items = RecommendItemsProvider.INSTANCE.getItems(userId, scope, excludeItems, 50, topSession, version);
		if (items != null && !items.isEmpty())
		{
			_logger.info("recent items count: " + items.size());
			List<Item> recentDelistItems = new ArrayList<Item>();
			for (Item item : items)
			{
				Date delistTime = item.getDelistTime();
				_logger.info("delistTime: " + delistTime);
				if (delistTime != null)
				{
					if (delistTime.getTime() - System.currentTimeMillis() < recentPeriod * DateUtils.MILLIS_PER_MINUTE)
					{
						_logger.info("recent item: " + item.getTitle());
						recentDelistItems.add(item);
					}
					else
					{
						break;
					}
				}
			}
			int recentDelistItemsCount = recentDelistItems.size();
			_logger.info("recent delist items count: " + recentDelistItemsCount);
			if (recentDelistItemsCount > 0)
			{
				if (recentDelistItemsCount > remainCount)
				{
					ItemsOnsaleGetResponse hasShowcaseRsp = TaobaoProxy.getHasShowcaseItems(topSession, 200);
					if (hasShowcaseRsp.isSuccess())
					{
						List<Item> hasShowcaseButLongToBeDelistItems = hasShowcaseRsp.getItems();
						if (hasShowcaseButLongToBeDelistItems != null)
						{
							for (Item item : hasShowcaseButLongToBeDelistItems)
							{
								if (!TaobaoService.contains(mustItems, item))
								{
									_logger.info("deleting recommend item: " + item.getNumIid() + ":" + item.getDelistTime());
									TaobaoProxy.deleteRecommendItem(item.getNumIid(), topSession);
									if (++remainCount >= recentDelistItemsCount)
									{
										break;
									}
								}
							}
						}
					}
					else
					{
						_logger.log(Level.SEVERE, TaobaoProxy.getError(hasShowcaseRsp));
					}
				}
				for (Item item : recentDelistItems)
				{
					--remainCount;
					TaobaoResponse rsp = recommend(item, userId, topSession);
					if (isOverflow(rsp))
					{
						break;
					}
				}
			}
		}
		return remainCount;
	}
	
	private TaobaoResponse recommend(Item item, long userId, String topSession) throws ApiException
	{
		if (topSession != null)
		{
			ItemRecommendAddResponse recommendRsp = TaobaoProxy.addRecommendItem(item.getNumIid(), topSession);
			Connection conn = null;
			try
			{
				conn = DBUtils.getConnection();
				String msg = recommendRsp.getSubMsg();
				if (recommendRsp.isSuccess())
				{
					//do nothing
				}
				else
				{
					_logger.log(Level.SEVERE, TaobaoProxy.getError(recommendRsp));
				}
				Dao.INSTANCE.addRecentRecommendedItem(item.getNumIid(), item.getTitle(), item.getPicUrl(), item.getDelistTime(), msg, userId, conn);
				_logger.info(userId + " added recent recommend item: " + item.getNumIid() + " : " + item.getDelistTime());
				return recommendRsp;
			}
			catch (Exception e)
			{
				_logger.log(Level.SEVERE, "", e);
			}
			finally
			{
				DBUtils.close(conn, null, null);
			}
		}
		return null;
	}
	
	private void clearLog(long userId)
	{
		Connection conn = null;
		try {
			conn = DBUtils.getConnection();
			int length = Dao.INSTANCE.getRecentRecommendedItemsCount(userId, conn);
			_logger.info("log length: " + length);
			if (length > 20)
			{
				List<Long> numIids = Dao.INSTANCE.getOldestRecommendedItem(userId, 5, conn);
				Dao.INSTANCE.deleteRecentRecommendedItems(numIids, conn);
			}
		} catch (NamingException e) {
			_logger.log(Level.SEVERE, "", e);
		} catch (SQLException e) {
			_logger.log(Level.SEVERE, "", e);
		}
		finally
		{
			DBUtils.close(conn, null, null);
		}
	}
	
	private boolean isInvalidSession(TaobaoResponse rsp)
	{
		return "27".equals(rsp.getErrorCode()) && rsp.getMsg().contains("Invalid session");
	}
	
	private void shutdown(long userId)
	{
		_logger.info("shutdown invalid session user: " + userId);
		RecommendEngine engine = new RecommendEngine(userId);
		try {
			engine.shutDown();
		} catch (SchedulerException e) {
			_logger.log(Level.SEVERE, "", e);
		}
	}
	
	private void increaseInvalidCount(long userId)
	{
		Integer invalidCount = invalidTable.get(userId);
		if (invalidCount == null)
		{
			invalidCount = 0;
		}
		invalidTable.put(userId, ++invalidCount);
		_logger.info("increase invalid session user: " + userId);
		_logger.info("" + invalidTable);
	}
	
	private void checkSession(long userId)
	{
		Integer invalidCount = invalidTable.get(userId);
		if (invalidCount != null && invalidCount > 3)
		{
			invalidTable.remove(userId);
			shutdown(userId);
		}
	}
}
